
// importScripts("./libshared/Point.js");
// importScripts("./libshared/Pbf.js");
// importScripts("./libshared/MVTData.js");
// importScripts("./libshared/MVTFeature.js");
// importScripts("./libshared/MVTLayer.js");

importScripts("./core/mathengine.js");
importScripts("./core/vector2.js");
importScripts("./core/vector3v117.js");
importScripts("./core/vector4.js");
importScripts("./core/matrix3f.js");
importScripts("./core/matrix4f.js");
importScripts("./core/matrix4d.js");
importScripts("./core/size.js");
importScripts("./core/bounds.js");
importScripts("./core/projection.js");
importScripts("./core/triangulator.js");
importScripts("./core/coordsys.js");
// importScripts("./core/tilecoordsys.js");

var JoinType = {
	Miter : 0,
	Round : 1,
	Bevel : 2,
	Butt : 3,
	Square : 4,
	Fakeround : 5,
	Flipbevel : 6
};

var LineJoinType = {
	Miter : 0,
	Round : 1,
	Bevel : 2
};

var LineCapType = {
	Butt : 3,
	Round : 1,
	Square : 4
};

function LineMeshOptions() {
	this.Width = 1.0;
	this.MiterLimit = 0.2;
	this.RoundLimit = 1.05;
	this.JoinType = JoinType.Round;
	this.CapType = JoinType.Round;
	this.BaseHeight = 0;
}

Object.assign(LineMeshOptions.prototype, {

	setLineWidth: function (width) {
		this.Width = width;
	},

	setJoinType: function (join) {
		this.JoinType = join;
	},

	setCapType: function (cap) {
		this.CapType = cap;
	},

	setBaseHeight: function (height) {
		this.BaseHeight = height;
	},

} );

function MeshData() {
	this.Edges = [];
	this.Vertices = [];
	this.Normals = [];
	this.Triangles = [];
	this.UV = [];
	this.IDs = [];
}

Object.assign(MeshData.prototype, {

	clear: function () {
		this.Edges = [];
		this.Vertices = [];
		this.Normals = [];
		this.Triangles = [];
		this.UV = [];
		this.IDs = [];
	},

} );

const WORLD_SIZE = 2 * Math.PI * 6378137;


function LineMeshTessellator() {
	this.Vector3Up = new Vector3(0, 0, 1);
	this.Vector3Unused = new Vector3(-3.40282347E+38, -3.40282347E+38, -3.40282347E+38);

	this._cosHalfSharpCorner = Math.cos(75 / 2 * (Math.PI / 180));
	this._sharpCornerOffset = 15;
	this._options = null;
	this._scaledWidth = 1.0;
	this._vertexList = [];
	this._normalList = [];
	this._triangleList = [];
	this._uvList = [];
	this._tangentList = [];

	this._index1 = -1;
	this._index2 = -1;
	this._index3 = -1;
	this._cornerOffsetA = 0;
	this._cornerOffsetB = 0;
	this._startOfLine = true;

	this._prevVertex = null;
	this._currentVertex = null;
	this._nextVertex = null;
	this._prevNormal = null;
	this._nextNormal = null;
	this._distance = 0;

	this.coordSys = new CoordSys();
}

Object.assign(LineMeshTessellator.prototype, {

	resetFields: function () {
		this._index1 = -1;
		this._index2 = -1;
		this._index3 = -1;
		this._startOfLine = true;
		this._cornerOffsetA = 0;
		this._cornerOffsetB = 0;

		this._vertexList = [];
		this._normalList = [];
		this._uvList = [];
		this._triangleList = [];

		this._prevVertex = this.Vector3Unused;
		this._currentVertex = this.Vector3Unused;
		this._nextVertex = this.Vector3Unused;

		this._prevNormal = this.Vector3Unused;
		this._nextNormal = this.Vector3Unused;
		this._distance = 0;
	},

	batchProcess: function(geoFeatures,center,options, needs2World=true){

		this._options = options;
		this._scaledWidth = options.Width;

		var meshDatas = [];
    
		var worldFeatures = geoFeatures;
		for (let i = 0; i < worldFeatures.length; i++) {
			let geometry = worldFeatures[i].geometry;
			let coords = geometry.coordinates;
			if ( geometry.type == "LineString" ) {
			
				if( coords.length>0 ){
					
					let  points = [];
					if(needs2World){
						for (let l = 0; l < coords.length; l++) {
							let position = this.projectToWorld([coords[l][0], coords[l][1], 0.2]);
							points.push(new Vector3(position.x-center.x, position.y-center.y, position.z));
						}
					} else {
						for (let l = 0; l < coords.length; l++) {
							points.push(new Vector3(coords[l][0], coords[l][1], 0.2));
						}
					}
					
					this.process(points,meshDatas);

				}
			}
			else if (geometry.type == "MultiLineString") {
				let lsize = coords.length;
				for(let j=0; j<lsize; j++) {
					let lcoord = coords[j];
				
					if (lcoord.length > 0) {
						let  points = [];
						if(needs2World){
							for (let l = 0; l < lcoord.length; l++) {
								let position = this.projectToWorld([lcoord[l][0], lcoord[l][1], 0.2]);
								points.push(new Vector3(position.x-center.x, position.y-center.y, position.z));
							}
						} else {
							for (let l = 0; l < lcoord.length; l++) {
								points.push(new Vector3(lcoord[l][0], lcoord[l][1], 0.2));
							}
						}
						this.process(points,meshDatas);
					}
				}
			}
		}

		return meshDatas;

	},

	//经纬度坐标到世界坐标
    projectToWorld(coords) {

		// Spherical mercator forward projection, re-scaling to WORLD_SIZE
		var cx = this.coordSys.mercatorXfromLng(coords[0]);
		var cy = this.coordSys.mercatorYfromLat(coords[1]);
		var projected = [ cx * WORLD_SIZE, cy * WORLD_SIZE ];
		var pixelsPerMeter = this.coordSys.projectedUnitsPerMeter(coords[1]);
		//z dimension
		var height = coords[2] || 0;
		projected.push(height * pixelsPerMeter);
		var result = new Vector3(projected[0], projected[1], projected[2]);
		return result;
	},
	
	//处理一条线
	process: function (points, meshdatas) {

		if (points.length < 1)
			return;

		var meshdata = null;
		if (meshdatas.length > 0) {
			meshdata = meshdatas[meshdatas.length - 1];
			if (meshdata.Triangles.length > 60000) {
				meshdata = new MeshData();
				meshdatas.push(meshdata);
			}
		} 
		else {
			meshdata = new MeshData();
			meshdatas.push(meshdata);
		}

		this.extrudeLine(points, meshdata);
	
	},

	extrudeLine: function (points, meshdata, lineID) {
		var roadSegment = points;
		if (roadSegment.length < 1)
			return;

		this.resetFields();

		var roadSegmentCount = roadSegment.length;
		for (var i = 0; i < roadSegmentCount; i++) {
			this._nextVertex = i != (roadSegmentCount - 1) ? roadSegment[i + 1] : this.Vector3Unused;

			if (!this._nextNormal.equals(this.Vector3Unused)) {
				this._prevNormal = this._nextNormal;
			}

			if (!this._currentVertex.equals(this.Vector3Unused)) {
				this._prevVertex = this._currentVertex;
			}

			this._currentVertex = roadSegment[i];

			var dir = new Vector3().subVectors(this._nextVertex, this._currentVertex).normalize();
			var nml = new Vector3().crossVectors(dir, this.Vector3Up).normalize();

			this._nextNormal = !(this._nextVertex.equals(this.Vector3Unused))
				? nml : this._prevNormal;

			if (this._prevNormal.equals(this.Vector3Unused)) {
				this._prevNormal = this._nextNormal.normalize();
			}

			var joinNormal = new Vector3().addVectors(this._prevNormal.normalize(), this._nextNormal.normalize()).normalize();

			/*  joinNormal     prevNormal
             *             ↖      ↑
             *                .________. prevVertex
             *                |
             * nextNormal  ←  |  currentVertex
             *                |
             *     nextVertex !
             *
             */
			var cosHalfAngle = joinNormal.x * this._nextNormal.x + joinNormal.y * this._nextNormal.y;
			if ( cosHalfAngle > 1.0 )	cosHalfAngle = 1.0;
			if ( cosHalfAngle < 0.01 )   cosHalfAngle = 0.01;
			var miterLength = cosHalfAngle != 0 ? 1 / cosHalfAngle : Number.POSITIVE_INFINITY;
			var isSharpCorner = cosHalfAngle < this._cosHalfSharpCorner && !this._prevVertex.equals(this.Vector3Unused) && !this._nextVertex.equals(this.Vector3Unused);

			if (isSharpCorner && i > 0) {
				var prevSegmentLength = new Vector3().subVectors(this._currentVertex, this._prevVertex).length();
				if (prevSegmentLength > 2 * this._sharpCornerOffset) {
					var dir = new Vector3().subVectors(this._currentVertex, this._prevVertex);
					var newPrevVertex = new Vector3().subVectors(this._currentVertex, dir.multiplyScalar(this._sharpCornerOffset / prevSegmentLength));
					this._distance += new Vector3().subVectors(newPrevVertex, this._prevVertex).length();
					this.addCurrentVertex(newPrevVertex, this._distance, this._prevNormal, meshdata);
					this._prevVertex = newPrevVertex;
				}
			}

			var middleVertex = !this._prevVertex.equals(this.Vector3Unused) && !this._nextVertex.equals(this.Vector3Unused);
			var currentJoin = middleVertex ? this._options.JoinType : this._options.CapType;

			if (middleVertex && currentJoin == JoinType.Round) {
				if (miterLength < this._options.RoundLimit) {
					currentJoin = JoinType.Miter;
				} else if (miterLength <= 2) {
					currentJoin = JoinType.Fakeround;
				}
			}

			if (currentJoin == JoinType.Miter && miterLength > this._options.MiterLimit) {
				currentJoin = JoinType.Bevel;
			}

			if (currentJoin == JoinType.Bevel) {
				// The maximum extrude length is 128 / 63 = 2 times the width of the line
				// so if miterLength >= 2 we need to draw a different type of bevel here.
				if (miterLength > 2) {
					currentJoin = JoinType.Flipbevel;
				}

				// If the miterLength is really small and the line bevel wouldn't be visible,
				// just draw a miter join to save a triangle.
				if (miterLength < this._options.MiterLimit) {
					currentJoin = JoinType.Miter;
				}
			}

			if (!this._prevVertex.equals(this.Vector3Unused)) {
				this._distance += new Vector3().subVectors(this._currentVertex, this._prevVertex).length();
			}

			if (currentJoin == JoinType.Miter) {
				joinNormal.multiplyScalar(miterLength);
				this.addCurrentVertex(this._currentVertex, this._distance, joinNormal, meshdata);
			} else if (currentJoin == JoinType.Flipbevel) {
				// miter is too big, flip the direction to make a beveled join

				if (miterLength > 100) {
					// Almost parallel lines
					joinNormal.copy(this._nextNormal).multiplyScalar(-1);
				} else {
					var direction = (this._prevNormal.x * this._nextNormal.y - this._prevNormal.y * this._nextNormal.x) > 0 ? 1 : -1;
					var bevelLength = miterLength * new Vector3().addVectors(this._prevNormal, this._nextNormal).length() / new Vector3().subVectors(this._prevNormal, this._nextNormal).length();
					joinNormal = joinNormal.cross(this.Vector3Up).multiplyScalar(bevelLength * direction);
				}

				this.addCurrentVertex(this._currentVertex, this._distance, joinNormal, meshdata, 0, 0);
				this.addCurrentVertex(this._currentVertex, this._distance, new Vector3().copy(joinNormal).multiplyScalar(-1), meshdata, 0, 0);
			} else if (currentJoin == JoinType.Bevel || currentJoin == JoinType.Fakeround) {
				var lineTurnsLeft = (this._prevNormal.x * this._nextNormal.y - this._prevNormal.y * this._nextNormal.x) < 0;
				var offset = -Math.sqrt(miterLength * miterLength - 1);
				if (lineTurnsLeft) {
					this._cornerOffsetB = 0;
					this._cornerOffsetA = offset;
				} else {
					this._cornerOffsetA = 0;
					this._cornerOffsetB = offset;
				}

				// Close previous segment with a bevel
				if (!this._startOfLine) {
					this.addCurrentVertex(this._currentVertex, this._distance, this._prevNormal, meshdata, this._cornerOffsetA, this._cornerOffsetB);
				}

				if (currentJoin == JoinType.Fakeround) {
					// The join angle is sharp enough that a round join would be visible.
					// Bevel joins fill the gap between segments with a single pie slice triangle.
					// Create a round join by adding multiple pie slices. The join isn't actually round, but
					// it looks like it is at the sizes we render lines at.

					// Add more triangles for sharper angles.
					// This math is just a good enough approximation. It isn't "correct".
					var n = Math.floor((0.5 - (cosHalfAngle - 0.5)) * 8);
					var approxFractionalJoinNormal = null;
					for (var m = 0; m < n; m++) {
						approxFractionalJoinNormal = new Vector3().addVectors(new Vector3().copy(this._nextNormal).multiplyScalar((m + 1) / (n + 1)), this._prevNormal).normalize();
						this.addPieSliceVertex(this._currentVertex, this._distance, approxFractionalJoinNormal, lineTurnsLeft, meshdata);
					}

					this.addPieSliceVertex(this._currentVertex, this._distance, joinNormal, lineTurnsLeft, meshdata);

					//change it to go -1, not sure if it's a good idea but it adds the last vertex in the corner,
					//as duplicate of next road segment start
					for (var k = n - 1; k >= -1; k--) {
						approxFractionalJoinNormal = new Vector3().addVectors(new Vector3().copy(this._prevNormal).multiplyScalar((k + 1) / (n + 1)), this._nextNormal).normalize();
						this.addPieSliceVertex(this._currentVertex, this._distance, approxFractionalJoinNormal, lineTurnsLeft, meshdata);
					}

					//ending corner
					this._index1 = -1;
					this._index2 = -1;
				}

				if (!this._nextVertex.equals(this.Vector3Unused)) {
					this.addCurrentVertex(this._currentVertex, this._distance, this._nextNormal, meshdata, -this._cornerOffsetA, -this._cornerOffsetB);
				}
			} else if (currentJoin == JoinType.Butt) {
				if (!this._startOfLine) {
					// Close previous segment with a butt
					this.addCurrentVertex(this._currentVertex, this._distance, this._prevNormal, meshdata, 0, 0);
				}

				// Start next segment with a butt
				if (this._nextVertex != this.Vector3Unused) {
					this.addCurrentVertex(this._currentVertex, this._distance, this._nextNormal, meshdata, 0, 0);
				}
			} else if (currentJoin == JoinType.Square) {
				if (!this._startOfLine) {
					// Close previous segment with a square cap
					this.addCurrentVertex(this._currentVertex, this._distance, this._prevNormal, meshdata, 1, 1);

					// The segment is done. Unset vertices to disconnect segments.
					this._index1 = this._index2 = -1;
				}

				// Start next segment
				if (!this._nextVertex.equals(this.Vector3Unused)) {
					this.addCurrentVertex(this._currentVertex, this._distance, this._nextNormal, meshdata, -1, -1);
				}
			} else if (currentJoin == JoinType.Round) {
				if (this._startOfLine) {
					this.addCurrentVertex(this._currentVertex, this._distance, new Vector3().copy(this._prevNormal).multiplyScalar(0.33), meshdata, -2, -2);
					this.addCurrentVertex(this._currentVertex, this._distance, new Vector3().copy(this._prevNormal).multiplyScalar(0.66), meshdata, -.7, -.7);
					this.addCurrentVertex(this._currentVertex, this._distance, this._prevNormal, meshdata, 0, 0);
				} else if (this._nextVertex.equals(this.Vector3Unused)) {
					this.addCurrentVertex(this._currentVertex, this._distance, this._prevNormal, meshdata, 0, 0);
					this.addCurrentVertex(this._currentVertex, this._distance, new Vector3().copy(this._prevNormal).multiplyScalar(0.66), meshdata, .7, .7);
					this.addCurrentVertex(this._currentVertex, this._distance, new Vector3().copy(this._prevNormal).multiplyScalar(0.33), meshdata, 2, 2);
					this._index1 = -1;
					this._index2 = -1;
				} else {
					this.addCurrentVertex(this._currentVertex, this._distance, this._prevNormal, meshdata, 0, 0);
					this.addCurrentVertex(this._currentVertex, this._distance, this._nextNormal, meshdata, 0, 0);
				}
			}

			if (isSharpCorner && i < roadSegmentCount - 1) {
				var nextSegmentLength = new Vector3().subVectors(this._currentVertex, this._nextVertex).length();
				if (nextSegmentLength > 2 * this._sharpCornerOffset) {
					var newCurrentVertex = new Vector3().addVectors(this._currentVertex, new Vector3().subVectors(this._nextVertex, this._currentVertex).multiplyScalar(this._sharpCornerOffset / nextSegmentLength)); //._round()
					this._distance += new Vector3().subVectors(newCurrentVertex, this._currentVertex).length();
					this.addCurrentVertex(newCurrentVertex, this._distance, this._nextNormal, meshdata);
					this._currentVertex = newCurrentVertex;
				}
			}

			this._startOfLine = false;
		}

		meshdata.Edges.push(meshdata.Vertices.length);
		meshdata.Edges.push(meshdata.Vertices.length + 1);
		meshdata.Edges.push(meshdata.Vertices.length + this._vertexList.length - 1);
		meshdata.Edges.push(meshdata.Vertices.length + this._vertexList.length - 2);

		meshdata.Vertices = meshdata.Vertices.concat(this._vertexList);
		meshdata.Normals = meshdata.Normals.concat(this._normalList);
		meshdata.Triangles = meshdata.Triangles.concat(this._triangleList);
		meshdata.UV = meshdata.UV.concat(this._uvList);
		
		if(lineID!==undefined && lineID!= -9999){
			
			let IDs = [];
			for(let k=0;k<this._vertexList.length;k++){
				IDs.push(lineID);
			}
			meshdata.IDs = meshdata.IDs.concat(IDs);
		}
	},

	addPieSliceVertex: function (vertexPosition, dist, normal, lineTurnsLeft, meshdata) {
		var triIndexStart = meshdata.Vertices.length;
		var extrude = new Vector3().copy(normal).multiplyScalar(lineTurnsLeft ? -1 : 1);
		var vert = new Vector3().addVectors(vertexPosition, extrude.multiplyScalar(this._scaledWidth));
		this._vertexList.push(vert);
		this._normalList.push(this.Vector3Up);
		this._uvList.push(new Vector2(1, dist));

		this._index3 = triIndexStart + this._vertexList.length - 1;
		if (this._index1 >= 0 && this._index2 >= 0) {
			this._triangleList.push(this._index1);
			this._triangleList.push(this._index3);
			this._triangleList.push(this._index2);
			if (!lineTurnsLeft) {
				meshdata.Edges.push(this._index3);
				meshdata.Edges.push(this._index1);
			} else {
				meshdata.Edges.push(this._index2);
				meshdata.Edges.push(this._index3);
			}
		}

		if (lineTurnsLeft) {
			this._index2 = this._index3;
		} else {
			this._index1 = this._index3;
		}
	},

	addCurrentVertex: function (vertexPosition, dist, normal, meshdata, endLeft, endRight) {
		var triIndexStart = meshdata.Vertices.length;
		var extrude = new Vector3().copy(normal);
		var dir = new Vector3().copy(normal).cross(this.Vector3Up);
		if (endLeft != undefined && endLeft != 0) {
			extrude = new Vector3().subVectors(extrude, new Vector3().copy(dir).multiplyScalar(endLeft));
		}

		var vert = new Vector3().addVectors(vertexPosition, extrude.multiplyScalar(this._scaledWidth));
		this._vertexList.push(vert);
		this._normalList.push(this.Vector3Up);
		this._uvList.push(new Vector2(1, dist));

		this._index3 = triIndexStart + this._vertexList.length - 1;
		if (this._index1 >= triIndexStart && this._index2 >= triIndexStart) {
			this._triangleList.push(this._index1);
			this._triangleList.push(this._index3);
			this._triangleList.push(this._index2);
			meshdata.Edges.push(triIndexStart + this._vertexList.length - 1);
			meshdata.Edges.push(triIndexStart + this._vertexList.length - 3);
		}

		this._index1 = this._index2;
		this._index2 = this._index3;

		extrude = new Vector3().add(normal).multiplyScalar(-1);
		if (endRight != undefined && endRight != 0) {
			extrude = new Vector3().subVectors(extrude, new Vector3().copy(dir).multiplyScalar(endRight));
		}

		var vert2 = new Vector3().addVectors(vertexPosition, extrude.multiplyScalar(this._scaledWidth));
		this._vertexList.push(vert2);
		this._normalList.push(this.Vector3Up);
		this._uvList.push(new Vector2(0, dist));

		this._index3 = triIndexStart + this._vertexList.length - 1;
		if (this._index1 >= triIndexStart && this._index2 >= triIndexStart) {
			this._triangleList.push(this._index1);
			this._triangleList.push(this._index2);
			this._triangleList.push(this._index3);
			meshdata.Edges.push(triIndexStart + this._vertexList.length - 3);
			meshdata.Edges.push(triIndexStart + this._vertexList.length - 1);
		}

		this._index1 = this._index2;
		this._index2 = this._index3;
	},

} );




